package apps.sim.action.handler;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.stream.StreamSource;

import apps.sim.core.AHandler;
import apps.sim.core.log.Console;
import apps.sim.core.log.Console.Color;
import apps.sim.core.log.Logger;

import com.sun.net.httpserver.HttpExchange;

public class SchedulerCallbackHandler extends AHandler {
	private ScheduledCallbackHandlerConfiguration configuration = new ScheduledCallbackHandlerConfiguration();
	private static ScheduledFuture<?> schedule = null;

	public byte[] handle(HttpExchange exchange, String context) throws Exception {
		final InputStream requestBody = exchange.getRequestBody();
		final String req = readInput(requestBody);
		if (this.configuration.isUseRequestPayload()) {
			scheduleCallback(req);
		} else {
			final StringBuilder fixed = this.configuration.getRequestPayload();
			scheduleCallback(fixed != null ? fixed.toString() : null);
		}
		return "Callack scheduled".getBytes();
	}

	private void scheduleCallback(final String payload) {
		final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
		if (schedule != null) {
			try {
				schedule.cancel(true);
			} catch (Throwable t) {
			}
		}
		if (!this.configuration.isRepeated()) {
			schedule = scheduledExecutorService.schedule(new Callable<Void>() {
				public Void call() throws Exception {
					if (configuration.isSoapService()) {
						soapCallback(payload);
					} else {
						httpCallback(payload);
					}
					return null;
				}

			}, this.configuration.getOffsetInSeconds(), TimeUnit.SECONDS);
		} else {
			schedule = scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
				@Override
				public void run() {
					try {
						if (configuration.isSoapService()) {
							soapCallback(payload);
						} else {
							httpCallback(payload);
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}, this.configuration.getOffsetInSeconds(), this.configuration.getOffsetInSeconds(), TimeUnit.SECONDS);

		}
		Console.print("Job scheduled. ID: " + schedule.hashCode(), Color.INFO);
	}

	private void soapCallback(String payload) throws Exception {
		final SOAPConnectionFactory soapConnFactory = SOAPConnectionFactory.newInstance();
		final SOAPConnection connection = soapConnFactory.createConnection();
		final MessageFactory messageFactory = MessageFactory.newInstance();
		final SOAPMessage message = messageFactory.createMessage();
		final SOAPPart soapPart = message.getSOAPPart();
		final StreamSource preppedMsgSrc = new StreamSource(new ByteArrayInputStream(payload.getBytes()));
		soapPart.setContent(preppedMsgSrc);
		message.saveChanges();
		Logger.log(payload);
		Console.print("Call back message to: " + this.configuration.getTargetEndpoint(), Color.INFO);
		connection.call(message, this.configuration.getTargetEndpoint());
		connection.close();
	}

	private void httpCallback(String payload) throws IOException {
		final URL url = new URL(this.configuration.getTargetEndpoint());
		final URLConnection connection = url.openConnection();
		connection.setDoOutput(true);
		final OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
		out.write(payload);
		out.close();
		final BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
		final StringBuilder builder = new StringBuilder();
		String decodedString;
		while ((decodedString = in.readLine()) != null) {
			builder.append(decodedString);
		}
		in.close();
		Logger.log("Call back response: " + builder);
	}

	@Override
	public String getContentType() {
		return "text/xml;charset=utf-8";
	}

	public String getDescription() {
		return "This handler provides support for scheduling a call back. Configure proper information and set offset in seconds";
	}

	public ScheduledCallbackHandlerConfiguration getHandlerConfiguration() {
		return configuration;
	}

	public void setHandlerConfiguration(IHandlerConfig config) {
		this.configuration = (ScheduledCallbackHandlerConfiguration) config;
	}

	public static class ScheduledCallbackHandlerConfiguration implements IHandlerConfig {
		private boolean repeated;
		private int offsetInSeconds;
		private String targetEndpoint;

		private boolean isSoapService;
		private boolean useRequestPayload;
		private StringBuilder requestPayload;

		public int getOffsetInSeconds() {
			return offsetInSeconds;
		}

		public StringBuilder getRequestPayload() {
			return requestPayload;
		}

		public String getTargetEndpoint() {
			return targetEndpoint;
		}

		public boolean isUseRequestPayload() {
			return useRequestPayload;
		}

		public boolean isSoapService() {
			return isSoapService;
		}

		public boolean isRepeated() {
			return repeated;
		}

		@Config(descrition = "Set true for repeated callback")
		public void setRepeated(boolean repeated) {
			this.repeated = repeated;
		}

		@Config(descrition = "Set true if this is a SOAP service")
		public void setSoapService(boolean isSoapService) {
			this.isSoapService = isSoapService;
		}

		@Config(descrition = "Set offset time from now when this will be triggered")
		public void setOffsetInSeconds(int offsetInSeconds) {
			this.offsetInSeconds = offsetInSeconds;
		}

		@Config(descrition = "Request payload for the service")
		public void setRequestPayload(StringBuilder requestPayload) {
			this.requestPayload = requestPayload;
		}

		@Config(descrition = "Target Endpoint for callback")
		public void setTargetEndpoint(String targetEndpoint) {
			this.targetEndpoint = targetEndpoint;
		}

		@Config(descrition = "If any payload sent with the request that will be for callback payload. Else fixed response will be used")
		public void setUseRequestPayload(boolean useRequestPayload) {
			this.useRequestPayload = useRequestPayload;
		}

		@Override
		public String asString() {
			return getOffsetInSeconds() + ARG_SEPARATOR + getRequestPayload() + ARG_SEPARATOR + getTargetEndpoint() + ARG_SEPARATOR + isUseRequestPayload() + ARG_SEPARATOR
					+ isSoapService() + ARG_SEPARATOR + isRepeated();
		}

		@Action(descrition = "THis will stop any running job if found")
		public String stopRunningJob() {
			if (schedule != null) {
				try {
					schedule.cancel(true);
					return "Running job stopped";
				} catch (Throwable t) {
					return "Problem killing running job stopped";
				}
			} else {
				return "No running job found";

			}
		}

		@Override
		public void fromString(String string) {
			String[] split = string.split(ARG_SEPARATOR_REGEX);
			this.setOffsetInSeconds(Integer.parseInt(split[0]));
			this.setRequestPayload(new StringBuilder(split[1]));
			this.setTargetEndpoint(split[2]);
			this.setUseRequestPayload(Boolean.parseBoolean(split[3]));
			this.setSoapService(Boolean.parseBoolean(split[4]));
			this.setRepeated(Boolean.parseBoolean(split[5]));
		}

		@Override
		public IHandlerConfig cloneInstance() {
			final ScheduledCallbackHandlerConfiguration config = new ScheduledCallbackHandlerConfiguration();
			config.setOffsetInSeconds(this.getOffsetInSeconds());
			config.setRequestPayload(this.getRequestPayload());
			config.setTargetEndpoint(this.getTargetEndpoint());
			config.setUseRequestPayload(this.isUseRequestPayload());
			config.setSoapService(this.isSoapService());
			config.setRepeated(this.isRepeated());
			return config;
		}
	}
}
